/*-------------------------------------------------------------------------
 *
 * proclang.c
 *	  PostgreSQL 语言支持代码。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/commands/proclang.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/table.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_language.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/proclang.h"
#include "miscadmin.h"
#include "parser/parse_func.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"


/*
 * 创建语言
 */
ObjectAddress CreateProceduralLanguage(CreatePLangStmt *fc_stmt)
{
	const char *fc_languageName = fc_stmt->plname;
	Oid			fc_languageOwner = GetUserId();
	Oid			fc_handlerOid,
				fc_inlineOid,
				fc_valOid;
	Oid			fc_funcrettype;
	Oid			fc_funcargtypes[1];
	Relation	fc_rel;
	TupleDesc	fc_tupDesc;
	Datum		fc_values[Natts_pg_language];
	bool		fc_nulls[Natts_pg_language];
	bool		fc_replaces[Natts_pg_language];
	NameData	fc_langname;
	HeapTuple	fc_oldtup;
	HeapTuple	fc_tup;
	Oid			fc_langoid;
	bool		fc_is_update;
	ObjectAddress fc_myself,
				fc_referenced;
	ObjectAddresses *fc_addrs;

	/*
	 * 检查权限
	 */
	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to create custom procedural language")));

	/*
	 * 查找 PL 处理函数并检查它是否返回期望的类型
	 */
	Assert(fc_stmt->plhandler);
	fc_handlerOid = LookupFuncName(fc_stmt->plhandler, 0, NULL, false);
	fc_funcrettype = get_func_rettype(fc_handlerOid);
	if (fc_funcrettype != LANGUAGE_HANDLEROID)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("function %s must return type %s",
						NameListToString(fc_stmt->plhandler), "language_handler")));

	/* 验证内联函数 */
	if (fc_stmt->plinline)
	{
		fc_funcargtypes[0] = INTERNALOID;
		fc_inlineOid = LookupFuncName(fc_stmt->plinline, 1, fc_funcargtypes, false);
		/* 返回值被忽略，因此我们不检查类型 */
	}
	else
		fc_inlineOid = InvalidOid;

	/* 验证验证器函数 */
	if (fc_stmt->plvalidator)
	{
		fc_funcargtypes[0] = OIDOID;
		fc_valOid = LookupFuncName(fc_stmt->plvalidator, 1, fc_funcargtypes, false);
		/* 返回值被忽略，因此我们不检查类型 */
	}
	else
		fc_valOid = InvalidOid;

	/* 可以创建它 */
	fc_rel = table_open(LanguageRelationId, RowExclusiveLock);
	fc_tupDesc = RelationGetDescr(fc_rel);

	/* 准备要插入的数据 */
	memset(fc_values, 0, sizeof(fc_values));
	memset(fc_nulls, false, sizeof(fc_nulls));
	memset(fc_replaces, true, sizeof(fc_replaces));

	namestrcpy(&fc_langname, fc_languageName);
	fc_values[Anum_pg_language_lanname - 1] = NameGetDatum(&fc_langname);
	fc_values[Anum_pg_language_lanowner - 1] = ObjectIdGetDatum(fc_languageOwner);
	fc_values[Anum_pg_language_lanispl - 1] = BoolGetDatum(true);
	fc_values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(fc_stmt->pltrusted);
	fc_values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(fc_handlerOid);
	fc_values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(fc_inlineOid);
	fc_values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(fc_valOid);
	fc_nulls[Anum_pg_language_lanacl - 1] = true;

	/* 检查是否存在预定义 */
	fc_oldtup = SearchSysCache1(LANGNAME, PointerGetDatum(fc_languageName));

	if (HeapTupleIsValid(fc_oldtup))
	{
		Form_pg_language fc_oldform = (Form_pg_language) GETSTRUCT(fc_oldtup);

		/* 存在一个；可以替换它吗？ */
		if (!fc_stmt->replace)
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("language \"%s\" already exists", fc_languageName)));

		/* 目前这没有意义，因为我们已经检查了超级用户 */
#ifdef NOT_USED
		if (!pg_language_ownercheck(fc_oldform->oid, fc_languageOwner))
			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_LANGUAGE,
						   fc_languageName);
#endif

		/*
		 * 不要更改现有的 oid、所有权或权限。请注意
		 * 下面的依赖更新代码必须与此决定一致。
		 */
		fc_replaces[Anum_pg_language_oid - 1] = false;
		fc_replaces[Anum_pg_language_lanowner - 1] = false;
		fc_replaces[Anum_pg_language_lanacl - 1] = false;

		/* 好的，执行... */
		fc_tup = heap_modify_tuple(fc_oldtup, fc_tupDesc, fc_values, fc_nulls, fc_replaces);
		CatalogTupleUpdate(fc_rel, &fc_tup->t_self, fc_tup);

		fc_langoid = fc_oldform->oid;
		ReleaseSysCache(fc_oldtup);
		fc_is_update = true;
	}
	else
	{
		/* 创建新的语言 */
		fc_langoid = GetNewOidWithIndex(fc_rel, LanguageOidIndexId,
									 Anum_pg_language_oid);
		fc_values[Anum_pg_language_oid - 1] = ObjectIdGetDatum(fc_langoid);
		fc_tup = heap_form_tuple(fc_tupDesc, fc_values, fc_nulls);
		CatalogTupleInsert(fc_rel, fc_tup);
		fc_is_update = false;
	}

	/*
	 * 为新语言创建依赖项。如果我们正在更新现有语言，首先删除任何现有的 pg_depend 条目。
	 * （但是，由于我们不更改所有权或权限，因此共享依赖关系不需要更改，我们保持不变。）
	 */
	fc_myself.classId = LanguageRelationId;
	fc_myself.objectId = fc_langoid;
	fc_myself.objectSubId = 0;

	if (fc_is_update)
		deleteDependencyRecordsFor(fc_myself.classId, fc_myself.objectId, true);

	/* 对语言所有者的依赖 */
	if (!fc_is_update)
		recordDependencyOnOwner(fc_myself.classId, fc_myself.objectId,
								fc_languageOwner);

	/* 对扩展的依赖 */
	recordDependencyOnCurrentExtension(&fc_myself, fc_is_update);

	fc_addrs = new_object_addresses();

	/* 对 PL 处理函数的依赖 */
	ObjectAddressSet(fc_referenced, ProcedureRelationId, fc_handlerOid);
	add_exact_object_address(&fc_referenced, fc_addrs);

	/* 对内联处理函数的依赖（如果有） */
	if (OidIsValid(fc_inlineOid))
	{
		ObjectAddressSet(fc_referenced, ProcedureRelationId, fc_inlineOid);
		add_exact_object_address(&fc_referenced, fc_addrs);
	}

	/* 对验证器函数的依赖（如果有） */
	if (OidIsValid(fc_valOid))
	{
		ObjectAddressSet(fc_referenced, ProcedureRelationId, fc_valOid);
		add_exact_object_address(&fc_referenced, fc_addrs);
	}

	record_object_address_dependencies(&fc_myself, fc_addrs, DEPENDENCY_NORMAL);
	free_object_addresses(fc_addrs);

	/* 新过程语言的创建后钩子 */
	InvokeObjectPostCreateHook(LanguageRelationId, fc_myself.objectId, 0);

	table_close(fc_rel, RowExclusiveLock);

	return fc_myself;
}

/*
 * get_language_oid - 给定语言名称，查找 OID
 *
 * 如果 missing_ok 为 false，当找不到语言名称时抛出错误。如果为
 * true，则返回 InvalidOid。
 */
Oid get_language_oid(const char *fc_langname, bool fc_missing_ok)
{
	Oid			fc_oid;

	fc_oid = GetSysCacheOid1(LANGNAME, Anum_pg_language_oid,
						  CStringGetDatum(fc_langname));
	if (!OidIsValid(fc_oid) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("language \"%s\" does not exist", fc_langname)));
	return fc_oid;
}
